using System;
using System.Collections.Generic;
using System.Text;
using SimpleMessageQueue;
using slcl.Packets;
using slcl.AssetSystem;
using slcl.InventorySystem;

namespace slcl
{
    /// <summary>
    /// specifies a specific spot in the grid.
    /// </summary>
    public class SimLocation
    {
        public string sim;
        public int x;
        public int y;
        public int z;

        public SimLocation()
        {
            this.sim = null;
            this.x = 0;
            this.y = 0;
            this.z = 0;
        }
        public SimLocation(string sim, int x, int y, int z)
        {
            this.sim = sim;
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public override string ToString()
        {
            return sim + "/" + x.ToString() + "/" + y.ToString() + "/" + z.ToString();
        }

        public string ToUri()
        {
            return "uri:" + sim.ToLower() + "&" + x.ToString() + "&" + y.ToString() + "&" + z.ToString();
        }
    }

    /// <summary>
    /// The One Class.
    /// The class that rules them all. 
    /// Contains everything necessary to conenct to and use SL.
    /// 
    /// Use Client.Command entries to do things like it's a single ojbect.
    /// If you want to get notificatios of other things, then you should register with 
    /// the individual managers directly.
    /// 
    /// We really need to document what exactly Client provides us.
    /// 
    /// </summary>
    /// 
    public partial class Client 
    {

        #region Enums
        public enum Event
        {
            StatsUpdate,
            BalanceChanged,
            HealthChanged,
            Log,
            ConfigurationChanged,
            Shutdown,
            Unknown
        }

        public enum Command
        {
            Shutdown,
            Configure,
            Pay,
            Login,
            Logout,
            Teleport,
            GetBalance,
            Unknown
        }

 
        #endregion

        #region Public Members

        public MessageQueue MQueue;

        public string FirstName;
        public string LastName;
        public string Password;

        /// <summary>
        /// our starting location. "last" "home" or Use Grid.StartLocation to format something special.
        /// </summary>
        public SimLocation StartLocation;

        /// <summary>Position avatar client will goto when login to 'home' or during
        /// teleport request to 'home' region.</summary>
        public LLVector3 HomePosition = LLVector3.Zero;

        /// <summary>
        /// LookAt point saved/restored with HomePosition
        /// </summary>
        public LLVector3 HomeLookAt = LLVector3.Zero;

        /// <summary>Gets the health of the agent</summary>
        public float Health { get { return health; } }

        /// <summary>Gets the current balance of the agent</summary>
        public int Balance { get { return balance; } }


        //        private TeleportStatus TeleportStat = TeleportStatus.None;
        //        private ManualResetEvent TeleportEvent = new ManualResetEvent(false);
        //        private uint HeightWidthGenCounter = 0;

        /// <summary>
        /// our client settings. Most are pre-connect. Some are post connect settable.
        /// </summary>
        public Settings Settings;

        public Grid Grid;
        public ObjectManager Objects;
        public MovementManager Movement;
        public GroupsManager Groups;
        public CommunicationManager Communication;
        public AssetManager Assets;
        public InventoryManager Inventory;
        public AppearanceManager Appearance;

        public Profile.Statistics ProfileStatistics = new Profile.Statistics();
        /// <summary>Avatar properties including about text, profile URL, image IDs and 
        /// publishing settings</summary>
        /// <remarks>If you change fields in this struct, the changes will not
        /// be reflected on the server until you call SetAvatarInformation</remarks>
        public Profile.AvatarProperties ProfileProperties = new Profile.AvatarProperties();
        /// <summary>Avatar interests including spoken languages, skills, and "want to"
        /// choices</summary>
        /// <remarks>If you change fields in this struct, the changes will not
        /// be reflected on the server until you call SetAvatarInformation</remarks>
        public Profile.Interests ProfileInterests = new Profile.Interests();

        public LLUUID ID = LLUUID.Zero;
        public uint LocalID = 0;

        #endregion Public Members


        #region Private data

        private float health = 0.0f;
        private int balance = 0;
        private bool Shutdown;

        /// <summary>
        /// how many times we've told the sim our window size
        /// </summary>
        uint HeightWidthGenCounter = 0;



        #endregion
        #region Contructors and Setup

        /// <summary>
        /// Use this method to setup the client manually.
        /// This allows for pre-init Settings manipulation.
        /// </summary>
        public Client()
        {
            CommonConstructor();
        }

        /// <summary>
        /// Your standard new client. It generates it's own thread and we interact with it via messages.
        /// </summary>
        /// <param name="f"></param>
        /// <param name="l"></param>
        /// <param name="p"></param>
        public Client(string f, string l, string p)
        {
            FirstName = f;
            LastName = l;
            Password = p;
            CommonConstructor();
            Init();
        }
 
        /// <summary>
        /// creates the common objects taht don't depend on Settings to exist for preferences.
        /// This is always automaticly called by Client constructor.
        /// </summary>
        void CommonConstructor()
        {
            Settings = new Settings(this);
            MQueue = new MessageQueue(this, true);
            MQueue.RegisterPacketCallback(Message.MessageType.Command
                , new MessageQueue.MessageCallback(HandleCommand));
            StartLocation = new SimLocation("last", 0, 0, 0);
            /// default Main grid.
            /// 
        }

        /// <summary>
        /// Call this once and ONLY once.
        /// </summary>
        /// <param name="UseIndependantQueues"></param>
        public void Init()
        {
            // the grid and it's simulators.
            // handles teleportation, login, and logout.
            Grid = new Grid(this, "Main", "https://login.agni.lindenlab.com/cgi-bin/login.cgi");

            // ahhh, objects, the life of the sim....
            Objects = new ObjectManager(this);
            Objects.RequestAllObjects = true;

            Movement = new MovementManager(this);           // Movement (walk, run, etc)
            Groups = new GroupsManager(this);               // group tasks
            Communication = new CommunicationManager(this); // for IM, chat, etc            
            Appearance = new AppearanceManager(this);       // our appearance.
            Inventory = new InventoryManager(this);         // our inventory.
            Assets = new AssetManager(this);                // our assets.

            // Health callback
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.HealthMessage, new CommandMessage.AckCallback(HealthHandler)), Grid);

            // Money callbacks
            CommandMessage.AckCallback callback = new CommandMessage.AckCallback(BalanceHandler);
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.MoneyBalanceReply, callback), Grid);
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.MoneySummaryReply, callback), Grid);
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.AdjustBalance, callback), Grid);

            // Event queue callback (used for Caps teleports currently)
            // FIXME: move the handlers to the right locations.
          //  MQueue.Send(new CommandMessage(Grid.Command.Register, new Caps.EventQueueCallback(EventQueueHandler));

        }

        #endregion

        #region Queue processing

        public void HandleCommand(Message msg)
        {
            if(!(msg is CommandMessage)) return;
            if(!(((CommandMessage)msg).Command is Command)) return;
            CommandMessage Msg = (CommandMessage)msg;
            switch ((Command)Msg.Command)
            {
                case Command.Login:
                    DoLogin(Msg);
                    break;
                case Command.Logout:
                    DoLogout(Msg);
                    break;
                case Command.Shutdown:
                    MQueue.Shutdown();
                    MQueue.Send(new CommandMessage(Grid.Command.Shutdown), Grid);
                    MQueue.Send(new CommandMessage(ObjectManager.Command.Shutdown), Objects);
                    break;
                default:
                    MQueue.Ack(Msg, AckMessage.StatusType.Failed, "Command not supported");
                    break;
            }
        }

        #endregion

        #region Utility Methods

        override public string ToString()
        {
            return FirstName + " " + LastName;
        }

        #endregion

        #region Login and Logout

        public void DoLogout(CommandMessage msg)
        {
            Log("Logging out client.", LogLevel.Info);
            Movement.UpdateTimer.Stop();
            MQueue.Send(new CommandMessage(Grid.Command.Logout, (null != msg ? msg.Data : null)), Grid);
        }

        /// <summary>
        /// blocking method for logging in client.
        /// </summary>
        /// <returns></returns>
        public bool DoLogin()
        {
            return DoLogin(null);
        }

        /// <summary>
        /// Asks Grid to login using the values provided for start location.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public  bool DoLogin(CommandMessage msg)
        {
            Log("Logging in client", LogLevel.Info);
            if (msg.Data is SimLocation)
                StartLocation = (SimLocation)msg.Data;
            CommandMessage Msg = new CommandMessage(Grid.Command.Login, true);
            // we are basicly just relaying this. It's the most simplistic login possible. Uses Main grid.
            MQueue.Send(Msg, Grid);
            Msg.StatusChanged.WaitOne();
            if (msg != null)
            {
                msg.AckCommand(Grid.Connected ? AckMessage.StatusType.Completed : AckMessage.StatusType.Failed);
            }
            if(Grid.Connected)
                    Movement.UpdateTimer.Start();
            return Grid.Connected ;
        }
        

        #endregion

        #region Logging

        public void Log(string msg, LogLevel lvl)
        {
            MQueue.MatchRegistered(new object[] { GlobalEvent.Log, lvl }
                , AckMessage.StatusType.Completed
                , new object[] { msg, lvl });
        }

        #endregion

        #region Profile

        /// <summary>
        /// Synchronize the local profile and interests information to the server
        /// </summary>
        public void SetAvatarInformation()
        {
            // Basic profile properties
            AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket();

            apup.AgentData.AgentID = this.ID;
            apup.AgentData.SessionID = Grid.SessionID;
            apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText);
            apup.PropertiesData.AllowPublish = this.ProfileProperties.AllowPublish;
            apup.PropertiesData.FLAboutText = Helpers.StringToField(this.ProfileProperties.FirstLifeText);
            apup.PropertiesData.FLImageID = this.ProfileProperties.FirstLifeImage;
            apup.PropertiesData.ImageID = this.ProfileProperties.ProfileImage;
            apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish;
            apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL);

            // Interests
            AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket();

            aiup.AgentData.AgentID = this.ID;
            aiup.AgentData.SessionID = Grid.SessionID;
            aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText);
            aiup.PropertiesData.SkillsMask = this.ProfileInterests.SkillsMask;
            aiup.PropertiesData.SkillsText = Helpers.StringToField(this.ProfileInterests.SkillsText);
            aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask;
            aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText);

            //Send packets
            Grid.SendPacket(apup);
            Grid.SendPacket(aiup);
        }

        #endregion

        #region Money
        /// <summary>
        /// Give Money to destination Avatar
        /// </summary>
        /// <param name="target">UUID of the Target Avatar</param>
        /// <param name="amount">Amount in L$</param>
        /// <param name="description">Reason (optional normally)</param>
        public void GiveMoney(LLUUID target, int amount, string description)
        {
            // 5001 - transaction type for av to av money transfers
            if (amount > 0)
                GiveMoney(target, amount, description, 5001);
            else
                Log("Attempted to pay zero or negative value " + amount, LogLevel.Warning);
        }

        /// <summary>
        /// Give Money to destionation Object or Avatar
        /// </summary>
        /// <param name="target">UUID of the Target Object/Avatar</param>
        /// <param name="amount">Amount in L$</param>
        /// <param name="description">Reason (Optional normally)</param>
        /// <param name="transactiontype">The type of transaction.  Currently only 5001 is
        /// documented for Av->Av money transfers.</param>
        public void GiveMoney(LLUUID target, int amount, string description, int transactiontype)
        {
            MoneyTransferRequestPacket money = new MoneyTransferRequestPacket();
            money.AgentData.AgentID = this.ID;
            money.AgentData.SessionID = Grid.SessionID;
            money.MoneyData.Description = Helpers.StringToField(description);
            money.MoneyData.DestID = target;
            money.MoneyData.SourceID = this.ID;
            money.MoneyData.TransactionType = transactiontype;
            money.MoneyData.AggregatePermInventory = 0; //TODO: whats this?
            money.MoneyData.AggregatePermNextOwner = 0; //TODO: whats this?
            money.MoneyData.Flags = 0; //TODO: whats this?
            money.MoneyData.Amount = amount;

            Grid.SendPacket(money);
        }

        /// <summary>
        /// Request the current L$ balance
        /// </summary>
        public void RequestBalance()
        {
            MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket();
            money.AgentData.AgentID = Grid.AgentID;
            money.AgentData.SessionID = Grid.SessionID;
            money.MoneyData.TransactionID = LLUUID.Zero;

            Grid.SendPacket(money);
        }

        /// <summary>
        /// Update Client Avatar's L$ balance from incoming packet
        /// </summary>
        /// <param name="packet">Incoming MoneyBalanceReplyPacket</param>
        /// <param name="simulator">Unused</param>
        private void BalanceHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Packet packet = carrier.packet;
            Simulator Simulator = carrier.sim;

            if (packet.Type == PacketType.MoneySummaryReply)
            {
                balance = ((MoneySummaryReplyPacket)packet).MoneyData.Balance;
            }
            else if (packet.Type == PacketType.AdjustBalance)
            {
                balance += ((AdjustBalancePacket)packet).AgentData.Delta;
            }
            else if (packet.Type == PacketType.MoneyBalanceReply)
            {
                MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet;
                balance = mbrp.MoneyData.MoneyBalance;
                // FIXME:

        //        if (OnMoneyBalanceReplyReceived != null)
          //      {
           //         try
            //        {
            //            OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID,
           //           mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance,
            //          mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted,
            //          Helpers.FieldToUTF8String(mbrp.MoneyData.Description));
            //        }
            //        catch (Exception e) { Client.Log(e.ToString(), LogLevel.Error); }
              //  }
            }
            MQueue.MatchRegistered(Event.BalanceChanged, AckMessage.StatusType.Completed, balance);
        }

        private void HealthHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            health = ((HealthMessagePacket)carrier.packet).HealthData.Health;
            MQueue.MatchRegistered(Event.HealthChanged, AckMessage.StatusType.Completed, health);
        }



        #endregion

        #region General

        /// <summary>
        /// Set the height and the width of the client window. This is used
        /// by the server to build a virtual camera frustum for our avatar
        /// </summary>
        /// <param name="height">New height of the viewer window</param>
        /// <param name="width">New width of the viewer window</param>
        public void SetHeightWidth(ushort height, ushort width)
        {
            AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket();
            heightwidth.AgentData.AgentID = Grid.AgentID;
            heightwidth.AgentData.SessionID = Grid.SessionID;
            heightwidth.AgentData.CircuitCode = Grid.CircuitCode;
            heightwidth.HeightWidthBlock.Height = height;
            heightwidth.HeightWidthBlock.Width = width;
            heightwidth.HeightWidthBlock.GenCounter = HeightWidthGenCounter++;

            Grid.SendPacket(heightwidth);
        }

	#endregion    
    }
}
